#include "Geometry.h"
#include "sutil.h"
#include "device_util.h"
#include <string>
#include <optixu/optixu_math_namespace.h>
#include "globals.h"

#include <optixu/optixu_matrix_namespace.h>
#include "ObjLoader.h"

using namespace optix;
using namespace std;

GeometryInstance Scene::createParallelogram( const float3& anchor,
                          const float3& offset1,
                          const float3& offset2,
                          const float3& color,
													const Material &mat)
{
  Geometry parallelogram = _ctx->createGeometry();
  parallelogram->setPrimitiveCount( 1u );
  parallelogram->setIntersectionProgram( _pgram_intersect );
  parallelogram->setBoundingBoxProgram( _pgram_bounding_box );

  float3 normal = normalize( cross( offset1, offset2 ) );
  float d       = dot( normal, anchor );
  float4 plane  = make_float4( normal, d );

  float3 v1 = offset1 / dot( offset1, offset1 );
  float3 v2 = offset2 / dot( offset2, offset2 );

  parallelogram["plane"]->setFloat( plane );
  parallelogram["anchor"]->setFloat( anchor );
  parallelogram["v1"]->setFloat( v1 );
  parallelogram["v2"]->setFloat( v2 );


  GeometryInstance gi = _ctx->createGeometryInstance( parallelogram,
      &mat,
      &mat+1 );
  gi["Kd"]->setFloat( color );
  gi["Ks"]->setFloat( 0.0f, 0.0f, 0.0f );
  gi["use_grid"]->setUint( 0u );
  gi["grid_color"]->setFloat( make_float3( 0.0f ) );
  gi["emissive_color"]->setFloat( 0.0f, 0.0f, 0.0f );

  return gi;
}

GeometryInstance Scene::createParallelogram( const float3& anchor,
                          const float3& offset1,
                          const float3& offset2,
                          const float3& color)
{
  Geometry parallelogram = _ctx->createGeometry();
  parallelogram->setPrimitiveCount( 1u );
  parallelogram->setIntersectionProgram( _pgram_intersect );
  parallelogram->setBoundingBoxProgram( _pgram_bounding_box );

  float3 normal = normalize( cross( offset1, offset2 ) );
  float d       = dot( normal, anchor );
  float4 plane  = make_float4( normal, d );

  float3 v1 = offset1 / dot( offset1, offset1 );
  float3 v2 = offset2 / dot( offset2, offset2 );

  parallelogram["plane"]->setFloat( plane );
  parallelogram["anchor"]->setFloat( anchor );
  parallelogram["v1"]->setFloat( v1 );
  parallelogram["v2"]->setFloat( v2 );


  GeometryInstance gi = _ctx->createGeometryInstance( parallelogram,
      &diffuseMat,
      &diffuseMat+1 );
  gi["Kd"]->setFloat( color );
  gi["Ks"]->setFloat( 0.0f, 0.0f, 0.0f );
  gi["use_grid"]->setUint( 0u );
  gi["grid_color"]->setFloat( make_float3( 0.0f ) );
  gi["emissive_color"]->setFloat( 0.0f, 0.0f, 0.0f );

  return gi;
}

GeometryInstance Scene::createSphere( const float4& params, const float3& color, Material mat) {
	Geometry sphere = _ctx->createGeometry();
	sphere->setPrimitiveCount( 1u );
	sphere->setIntersectionProgram( _sphere_intersect );
	sphere->setBoundingBoxProgram( _sphere_bounding_box );
	sphere["sphere"]->setFloat(params);

	GeometryInstance gi = _ctx->createGeometryInstance( sphere, &mat, &mat+1 );
	gi["Kd"]->setFloat( 0.0f, 0.0f, 0.0f );
  gi["Ks"]->setFloat( 1.0f, 1.0f, 1.0f );
  gi["use_grid"]->setUint( 0u );
  gi["grid_color"]->setFloat( make_float3( 0.0f ) );
  gi["emissive_color"]->setFloat( 0.0f, 0.0f, 0.0f );

	return gi;
}

string ptxpath( const std::string& target, const std::string& base )
{
  static std::string path;
  path = std::string(sutilSamplesPtxDir()) + "\\" + target + "_generated_" + base + ".ptx";
  return path.c_str();
}

Scene::Scene(Context ctx):_ctx(ctx) {  
	const std::string diffusePtxPath = ptxpath( "sample1", "plain.cu" );
	const std::string glassPtxPath = ptxpath( "sample1", "glass.cu" );

	

	diffuseMat = _ctx->createMaterial();
	diffuseMat->setClosestHitProgram( RAY_TYPE_VIEW, _ctx->createProgramFromPTXFile( diffusePtxPath,
																		"closest_hit") );
	 
	glassMat = _ctx->createMaterial(); 
	glassMat->setClosestHitProgram( RAY_TYPE_VIEW, _ctx->createProgramFromPTXFile( glassPtxPath,
																	"closest_hit") ); 

	glassMat->setClosestHitProgram( RAY_TYPE_PHOTON, _ctx->createProgramFromPTXFile( glassPtxPath,
                                   "closest_hit") );  
	// Any-hit shadow ray
	glassMat->setAnyHitProgram( RAY_TYPE_SHADOW, _ctx->createProgramFromPTXFile( glassPtxPath,
                                   "glassShadowHit") ); 

	
	Program ray_miss_program = _ctx->createProgramFromPTXFile( glassPtxPath, "radiance_miss" );
	_ctx->setMissProgram(0, ray_miss_program);
  

	glassMat["max_depth"]->setInt(5);
	glassMat["shadow_transparency"]->setFloat( 0.8f );
	glassMat["importance_cutoff"  ]->setFloat( 0.001f );
  glassMat["cutoff_color"       ]->setFloat( 0.2f, 0.2f, 0.2f );
  glassMat["fresnel_exponent"   ]->setFloat( 4.0f );
  glassMat["fresnel_minimum"    ]->setFloat( 0.1f );
  glassMat["fresnel_maximum"    ]->setFloat( 1.0f );
  glassMat["refraction_index"   ]->setFloat( 1.4f );
  glassMat["refraction_color"   ]->setFloat( 0.99f, 0.99f, 0.99f );
  glassMat["reflection_color"   ]->setFloat( 0.99f, 0.99f, 0.99f );
  glassMat["refraction_maxdepth"]->setInt( 2 );
  glassMat["reflection_maxdepth"]->setInt( 2 );
  float3 extinction = make_float3(1);
  glassMat["extinction_constant"]->setFloat( log(extinction.x), log(extinction.y), log(extinction.z) );
  glassMat["shadow_attenuation"]->setFloat( 1.0f, 1.0f, 1.0f );

	// Set up geometry programs
	std::string ptx_path = ptxpath( "sample1", "parallelogram.cu" );
  _pgram_bounding_box = _ctx->createProgramFromPTXFile( ptx_path, "bounds" );
  _pgram_intersect = _ctx->createProgramFromPTXFile( ptx_path, "intersect" );
	
  ptx_path = ptxpath( "sample1", "sphere.cu" );
	_sphere_bounding_box = _ctx->createProgramFromPTXFile( ptx_path, "bounds" );
  _sphere_intersect = _ctx->createProgramFromPTXFile( ptx_path, "intersect" );
	

	// Populate cornell box

	// create geometry instances
  std::vector<GeometryInstance> gis;

  const float3 white = make_float3( 0.8f, 0.8f, 0.8f );
  const float3 green = make_float3( 0.05f, 0.8f, 0.05f );
  const float3 red   = make_float3( 0.8f, 0.05f, 0.05f );
  const float3 black = make_float3( 0.0f, 0.0f, 0.0f );
  const float3 light = make_float3( 15.0f, 15.0f, 5.0f );
 
  // Floor 
	float boxSide = 400.f;
	gis.push_back( createParallelogram( make_float3( -boxSide/2, 0.0f, -boxSide/2 ),
                                      make_float3( 0.0f, 0.0f, boxSide ),
                                      make_float3( boxSide, 0.0f, 0.0f ),
                                      white ) ); 
	
	// Ceiling
  gis.push_back( createParallelogram( make_float3( -boxSide/2, boxSide, -boxSide/2 ),
                                      make_float3( 0.0f, 0.0f, boxSide ),
                                      make_float3( boxSide, 0.0f, 0.0f ),
                                      white ) ); 

  // Back wall
  gis.push_back( createParallelogram( make_float3( boxSide/2, 0.0f, -boxSide/2),
                                      make_float3( 0.0f, boxSide, 0.0f),
                                      make_float3( -boxSide, 0.0f, 0.0f),
                                      white ) ); 

  // Left wall 
  gis.push_back( createParallelogram( make_float3( -boxSide/2, 0.0f, -boxSide/2 ),
                                      make_float3( 0.0f, boxSide, 0.0f ),
                                      make_float3( 0.0f, 0.0f, boxSide ),
                                      green ) );

  // Right wall
  gis.push_back( createParallelogram( make_float3( boxSide/2, 0.0f, -boxSide/2 ),
                                      make_float3( 0.0f, 0.0f, boxSide ),
                                      make_float3( 0.0f, boxSide, 0.0f ),
                                      red ) );

	// Sphere
  //gis.push_back( createSphere( make_float4( 423.0f, 50.0f, 247.0f, 50.0f ), red, glassMat ) );


  // Light
	float lightSide = 100.f;
	  gis.push_back( createParallelogram( make_float3( -lightSide/2, boxSide, -lightSide/2),
                                      make_float3( 0.0f, 0.0f, lightSide),
                                      make_float3( lightSide, 0.0f, 0.0f),
                                      black) );
  gis.back()["emissive_color"]->setFloat( light );
	
	Light _light; 
	_light.anchor = make_float3( -lightSide/2, boxSide, -lightSide/2);
  _light.v1     = make_float3( 0.0f, 0.0f, lightSide);
  _light.v2     = make_float3( lightSide, 0.0f, 0.0f);
  _light.direction = normalize(cross( _light.v1, _light.v2 ) ); 
  _light.power  = make_float3( 0.5e6f, 0.4e6f, 0.2e6f );
  _ctx["light"]->setUserData( sizeof(Light), &_light );

	


	// Set transformations
  const float matrix_0[4*4] = { 20,  0,  0,  0, 
                                0,  20,  0,  0, 
                                0,  0,  20,  0, 
                                0,  0,  0,  1 };

	 

  const optix::Matrix4x4 m0( matrix_0 );

  std::string prog_path = std::string(sutilSamplesPtxDir()) + "/sample1_generated_triangle_mesh_iterative.cu.ptx";
  Program mesh_intersect = _ctx->createProgramFromPTXFile( prog_path, "mesh_intersect" );

	GeometryGroup ggGlass = _ctx->createGeometryGroup();

  ObjLoader objloader0( "D:/photon/sample1/wineglass.obj", _ctx, ggGlass, glassMat );
  objloader0.setIntersectProgram( mesh_intersect );
  objloader0.load( m0 );
	 

  // Create geometry group
  GeometryGroup geometry_group = _ctx->createGeometryGroup();
  geometry_group->setChildCount( static_cast<unsigned int>( gis.size() + 1 ) );
  
	for ( unsigned int i = 0; i < gis.size(); ++i )
    geometry_group->setChild( i, gis[i] );
  geometry_group->setChild( gis.size(), ggGlass->getChild(0) );

	geometry_group->setAcceleration( _ctx->createAcceleration("Bvh","Bvh") );

  _ctx["top_object"]->set( geometry_group );
  _ctx["top_shadower"]->set( geometry_group );
}
